package com.ylg.mybatis.core;

import javassist.ClassPool;
import javassist.CtClass;
import javassist.CtMethod;

import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

/**
 * @since 1.0
 * @author 杨凌广
 * @version 1.0
 *
 * 执行sql语句的Sql会话对象
 */
public class SqlSession {

    private SqlSessionFactory sqlSessionFactory;

    private Transaction transaction;

    private static ThreadLocal<SqlSession> threadLocal = new ThreadLocal<>();

    public SqlSession(SqlSessionFactory sqlSessionFactory, boolean autoCommit) {
        this.sqlSessionFactory = sqlSessionFactory;

        // 重新创建事务管理器对象 每一次获取会话对象都需要不同的连接 保证不是同一事务
        transaction = new JdbcTransaction(sqlSessionFactory.getTransaction().getDataSource(), autoCommit);
        // 打开连接对象 为connection赋值
        transaction.openConnection();
        threadLocal.set(this);
    }

    public static SqlSession getSqlSession() {
        return threadLocal.get();
    }

    /**
     * 插入数据
     * @param sqlId 唯一标识
     * @param obj 插入的对象
     * @return
     */
    public int insert(String sqlId, Object obj) {

        int count = 0;

        try {
            // 获取连接
            Connection connection = transaction.getConnection();
            // 取出sql语句
            String mapperSql = sqlSessionFactory.getMappedStatements().get(sqlId).getSql();

            // 将#{*} 替换为 ?
            // #\\{[0-9A-Za-z$_]*}
            String sql = mapperSql.replaceAll("#\\{[0-9A-Za-z$_]*}", "?");

            // 预编译sql
            PreparedStatement ps = connection.prepareStatement(sql);

            // 给占位符赋值
            // 获取有多少个占位符
            List<String> sqlFiled = getSqlFiled(mapperSql);
            for(int i = 0; i < sqlFiled.size(); i++) {
                // 获取要添加对象的属性值
                String filed = sqlFiled.get(i);
                String methodName = "get" + filed.toUpperCase().charAt(0) + filed.substring(1);
                Method getMethod = obj.getClass().getDeclaredMethod(methodName);
                Object invoke = getMethod.invoke(obj);
                ps.setString(i + 1, invoke.toString());
            }

            // 执行sql
            count = ps.executeUpdate();
        } catch (Exception e) {

        }

        return count;

    }

    /**
     * 查询sql
     * @param sqlId 唯一标识
     * @param param 条件查询
     * @return
     */
    public Object selectOne(String sqlId, Object param) {

        Object resultObj = null;

        try {
            // 获取连接
            Connection connection = transaction.getConnection();
            // 获取sql
            MappedStatement mappedStatement = sqlSessionFactory.getMappedStatements().get(sqlId);
            String mapperSql = mappedStatement.getSql();
            String resultType = mappedStatement.getResultType();

            String sql = mapperSql.replaceAll("#\\{[a-zA-Z0-9$_]*}", "?");

            // List<String> sqlFiled = getSqlFiled(mapperSql);

            // 预编译sql
            PreparedStatement ps = connection.prepareStatement(sql);

            // TODO 这种用于处理多个参数，或者为map或者为实体类的情况 这版本暂时不做处理
//            for (int i = 0; i < sqlFiled.size(); i++) {
//
//            }

            // 假设只有一个参数，并且为字符串类型
            ps.setString(1, param.toString());

            // 执行sql
            ResultSet resultSet = ps.executeQuery();


            if(resultSet.next()) {
                // 创建对象
                Class<?> aClass = Class.forName(resultType);
                resultObj = aClass.getDeclaredConstructor().newInstance();
                ResultSetMetaData metaData = resultSet.getMetaData();
                for(int i = 0; i < metaData.getColumnCount(); i++) {
                    String columnName = metaData.getColumnName(i + 1);
                    Method setMethod = aClass.getDeclaredMethod("set" + columnName.toUpperCase().charAt(0) + columnName.substring(1), String.class);
                    setMethod.invoke(resultObj, resultSet.getString(columnName));
                }
            }

        } catch (Exception e) {

        }

        return resultObj;
    }


    /**
     * 根据id进行修改
     * @param sqlId sql唯一标识
     * @param obj 要修改的对象
     * @return
     */
    public int updateById(String sqlId, Object obj) {

        int count = 0;

        try {
            // 获取连接
            Connection connection = transaction.getConnection();

            // 获取sql语句
            String mapperSql = sqlSessionFactory.getMappedStatements().get(sqlId).getSql();

            // 替换占位符
            String sql = mapperSql.replaceAll("#\\{[a-zA-Z0-9$_]*}", "?");

            // 预编译sql
            PreparedStatement ps = connection.prepareStatement(sql);

            // 获取参数
            List<String> sqlFiled = getSqlFiled(mapperSql);

            for(int i = 0; i < sqlFiled.size(); i++) {
                // 获取要添加对象的属性值
                String filed = sqlFiled.get(i);
                String methodName = "get" + filed.toUpperCase().charAt(0) + filed.substring(1);
                Method getMethod = obj.getClass().getDeclaredMethod(methodName);
                Object invoke = getMethod.invoke(obj);
                ps.setString(i + 1, invoke.toString());
            }

            // 执行sql
            count = ps.executeUpdate();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return count;

    }

    /**
     * 删除记录
     * @param sqlId
     * @param param
     */
    public int deleteById(String sqlId, Object param) {

        int count = 0;

        try {
            // 获取连接
            Connection connection = transaction.getConnection();

            // 获取sql语句
            String mapperSql = sqlSessionFactory.getMappedStatements().get(sqlId).getSql();

            // 替换占位符
            String sql = mapperSql.replaceAll("#\\{[a-zA-Z0-9$_]*}", "?");

            // 预编译sql
            PreparedStatement ps = connection.prepareStatement(sql);

           ps.setString(1, param.toString());

            // 执行sql
            count = ps.executeUpdate();

        } catch (Exception e) {
            throw new RuntimeException(e);
        }

        return count;
    }

    /**
     * 获取sql语句中#{} 括号里面的值 为了传值使用
     * @param mapperSql
     * @return
     */
    public List<String> getSqlFiled(String mapperSql) {

        List<String> fileds = new ArrayList<>();
        int firstJingIndex = -1;
        int firstYoukhIndex = -1;

        while(true) {
            // 第一次出现#的位置
            firstJingIndex = mapperSql.indexOf("#", firstJingIndex + 1);
            firstYoukhIndex = mapperSql.indexOf("}", firstYoukhIndex + 1);
            if(firstJingIndex == -1) {
                // 说明全部取出
                break;
            }
            String filed = mapperSql.substring(firstJingIndex + 2, firstYoukhIndex);
            fileds.add(filed);
        }

        return fileds;

    }

    public void commit() {
        transaction.commit();
    }

    public void rollback() {
        transaction.rollback();
    }

    public void close() {
        transaction.close();
    }


    /**
     * @since 1.2
     * 动态生成接口实现类
     * @param mapperClass 要生成的目标接口
     * @return
     * @param <T>
     */
    public <T> T getMapper(Class<T> mapperClass) throws Exception {

        // 获取类池 这个类池就是用来给我生成class的
        ClassPool pool = ClassPool.getDefault();
        // 制造类
        CtClass ctClass = pool.makeClass(mapperClass.getName() + "$proxy");
        // 制造接口
        CtClass ctInterface = pool.makeInterface(mapperClass.getName());
        // 实现接口
        ctClass.addInterface(ctInterface);
        // 实现接口中所有方法
        Method[] methods = mapperClass.getDeclaredMethods();

        // 拼接方法
        List<StringBuilder> methodList = Arrays.stream(methods).map(method -> {
            StringBuilder methodString = new StringBuilder();
            methodString.append("public ");
            methodString.append(method.getReturnType().getName() + " ");
            methodString.append(method.getName());
            methodString.append("(");
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                methodString.append(parameterTypes[i].getName() + " arg" + i);
                if(i < parameterTypes.length - 1) {
                    methodString.append(",");
                }
            }
            methodString.append(")");
            methodString.append("{");
            // 根据接口名和方法名获取sqlId，判断要操作什么类型的sql
            String sqlId = mapperClass.getName() + "." + method.getName();
            MappedStatement mappedStatement = sqlSessionFactory.getMappedStatements().get(sqlId);
            String sqlType = mappedStatement.getSqlType();

            methodString.append("com.ylg.mybatis.core.SqlSession sqlSession = com.ylg.mybatis.core.SqlSession.getSqlSession();");
            switch (sqlType.toUpperCase()) {

                case "UPDATE":
                    methodString.append("return sqlSession.updateById(\"" + sqlId + "\", arg0);");
                    break;
                case "INSERT":
                    methodString.append("return sqlSession.insert(\"" + sqlId + "\", arg0);");
                    break;
                case "SELECT":
                    methodString.append("return (" + method.getReturnType().getName() + ") sqlSession.selectOne(\"" + sqlId +"\", arg0);");
                    break;
                case "DELETE":
                    methodString.append("return sqlSession.deleteById(\"" + sqlId + "\", arg0);");
                    break;

            }

            methodString.append("}");
            return methodString;
        }).collect(Collectors.toList());

        // 将所有方法加入到实现类中
        for (StringBuilder methodString : methodList) {
            CtMethod method = CtMethod.make(methodString.toString(), ctClass);
            ctClass.addMethod(method);
        }

        // 在内存中生成class,加载到jvm虚拟机中，并且把字节码信息Class返回
        Class<?> proxyClass = ctClass.toClass();

        return (T) proxyClass.newInstance();

    }




}
